/***************************************************************************
 *   oboe.controllers.GridController                                       *
 *   Copyright (C) 2011 by Felipe Manga                                    *
 *   lufeboma@gmail.com                                                    *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

package oboe.controllers
{
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.geom.Matrix;
	import flash.utils.Dictionary;
	
	import oboe.core.Oboe;
	import oboe.math.Mesh3D;
	import oboe.math.Number2D;
	import oboe.math.Number3D;
	
	public class GridController extends NodeController
	{
		private var textureGridSize:int;
		private var textureTileSize:int;
		
		private var gridSize:Number2D;
		private var uv:Vector.<Number>;
		private var mesh:Vector.<Number3D>;
		
		public function GridController( heightMap:Vector.<Number>, tiles:Number2D, tileSize:Number2D, textureCount:int, textureSize:int, smooth:Boolean=false )
		{
			tileSize.mul( Oboe.flags["SCALE_3D"] );
			var texture:BitmapData = prepareTexture( textureCount, textureSize );
			super( prepareMesh( heightMap, tiles, tileSize, smooth ) );
			this.renderSettings.texture = texture;
		}
		
		private static const tmp3:Number3D = new Number3D(0,0,0);
		public override function slot_onGet3DPosition( p:Number3D ):void
		{
			tmp3.y = 0;
			tmp3.x = ((2*p.y-p.x)/2);
			tmp3.z =(p.x+tmp3.x);
			tmp3.add3( this.renderSettings.position );
			this.getGroundHeightAt( tmp3 );
			p.x = tmp3.x+tmp3.y;
			p.z = tmp3.z+tmp3.y;
			p.y = 0;
		}
		
		private static const tmatrix:Matrix = new Matrix();
		protected function setTexture( texture:DisplayObject, id:int ):void
		{
			var w:Number = (this.renderSettings.texture.width-1)/this.textureGridSize;
			tmatrix.identity();
			var ty:int = int( id/this.textureGridSize );
			var tx:int = id-ty*this.textureGridSize;
			tmatrix.ty = ty*this.textureTileSize;
			tmatrix.tx = tx*this.textureTileSize;
			tmatrix.a  = this.textureTileSize/texture.width;
			tmatrix.d  = this.textureTileSize/texture.height;
			this.renderSettings.texture.draw( texture, tmatrix, null, null, null, true );
		}
		
		public function getTilePosition( tileX:int, tileY:int ):Number3D
		{
			var r:Number3D = new Number3D( 
				tileX*this.renderSettings.scale.x+this.renderSettings.position.x + 0.5,
				0,
				tileY*this.renderSettings.scale.z+this.renderSettings.position.z + 0.5
				);
			
			r.y = this.mesh[ ((tileY)*this.gridSize.x+tileX)*6 ].y
				+ this.mesh[ ((tileY)*this.gridSize.x+tileX+1)*6 ].y
				+ this.mesh[ ((tileY+1)*this.gridSize.x+tileX+1)*6 ].y
				+ this.mesh[ ((tileY+1)*this.gridSize.x+tileX)*6 ].y;
			
			r.y = r.y*0.25*this.renderSettings.scale.y+this.renderSettings.position.y;
			
			this.renderSettings.rotator.apply( r );
			
			return r;
		}
		
		public function getTileHeightQuick( tileX:int, tileY:int ):Number
		{
			var w:int = this.gridSize.x;
			var ry:Number;
			ry  = this.mesh[ (tileY*w+tileX)*6 ].y
				+ this.mesh[ (tileY*w+tileX)*6+1 ].y
				+ this.mesh[ (tileY*w+tileX)*6+2 ].y
				+ this.mesh[ (tileY*w+tileX)*6+5 ].y;
			
			ry *= 0.25;
			
			return ry;
		}
		

		private static const tmp:Number3D = new Number3D(0,0,0);
		private static const tmp2:Number3D=new Number3D(0,0,0);
		protected function getGroundHeightAt( pos:Number3D ):void
		{
			var base:Number3D = tmp2;
			var weight:Number3D = tmp;
			var sX:int = this.gridSize.x;
			var sY:int = this.gridSize.y;
			weight.copy3( pos ).sub3( this.renderSettings.position ).div3( this.renderSettings.scale );
			base.copy3( weight ).floor();
			weight.sub3( base );
			
			if( base.z >= sY-1 || base.z < 0 || base.x >= sX-1 || base.x < 0 || weight.x < 0 || weight.z < 0 )
			{
				pos.y = -60;
				return;
			}
			
			pos.y = this.mesh[ (base.z*sX+base.x)*6 ].y*(1-weight.x)*(1-weight.z) +
				this.mesh[ (base.z*sX+base.x)*6+2 ].y*(1-weight.x)*(weight.z) +
				this.mesh[ (base.z*sX+base.x)*6+1 ].y*(weight.x)*(1-weight.z) +
				this.mesh[ (base.z*sX+base.x)*6+5 ].y*(weight.x)*(weight.z);
			
			pos.y *= this.renderSettings.scale.y;
			pos.y += this.renderSettings.position.y;
			// trace( "px:", pos.x, "pz:", pos.z, "baseX:", base.x, "baseZ:", base.z, "y:", pos.y );
		}
		
		protected function setTileTexture( tileX:int, tileY:int, id:int ):void
		{
			var tyb:Number = int( id/this.textureGridSize );
			var txb:Number = id-tyb*this.textureGridSize;
			var tye:Number = tyb+1;
			var txe:Number = txb+1;
			var v:int = (tileY*(this.gridSize.x) + tileX)*6*2;
			
			tyb /= this.textureGridSize;
			txb /= this.textureGridSize;
			tye /= this.textureGridSize;
			txe /= this.textureGridSize;
			
			this.uv[ v++ ]=txb; this.uv[ v++ ]=tyb;
			this.uv[ v++ ]=txe; this.uv[ v++ ]=tyb;
			this.uv[ v++ ]=txb; this.uv[ v++ ]=tye;
			this.uv[ v++ ]=txb; this.uv[ v++ ]=tye;
			this.uv[ v++ ]=txe; this.uv[ v++ ]=tyb;
			this.uv[ v++ ]=txe; this.uv[ v++ ]=tye;
		}
		
		private function prepareMesh( heightMap:Vector.<Number>, tiles:Number2D, tileSize:Number2D, smooth:Boolean ):Mesh3D
		{
			tiles.floor().flip();
			this.gridSize = new Number2D( tiles.x, tiles.y );
			var frames:Vector.< Vector.<Number3D> > = new Vector.<Vector.<Number3D>>(1, true);
			this.uv = new Vector.<Number>( (tiles.x)*(tiles.y)*6*2, true );
			this.mesh = new Vector.<Number3D>( (tiles.x)*(tiles.y)*6, true );
			frames[0] = mesh;
			var y:int;
			var x:int;
			var w:int = tiles.x;
			var h:int = tiles.y;
			var i:int = 0;
			var m:Number = 0;
			var tm:Number = 1/this.textureGridSize;
			
			for( y = 0; y < h; ++y )
			{
				for( x = 0; x < w; ++x )
				{
					mesh[ i++ ] = new Number3D( (y)*tileSize.y, heightMap[ (y)*w+x ], (x)*tileSize.x );
					mesh[ i++ ] = new Number3D( (y)*tileSize.y, heightMap[ (y)*w+x+1 ], (x+1)*tileSize.x );
					mesh[ i++ ] = new Number3D( (y+1)*tileSize.y, heightMap[ (y+1)*w+x ], (x)*tileSize.x );
					mesh[ i++ ] = new Number3D( (y+1)*tileSize.y, heightMap[ (y+1)*w+x ], (x)*tileSize.x );
					mesh[ i++ ] = new Number3D( (y)*tileSize.y, heightMap[ (y)*w+x+1 ], (x+1)*tileSize.x );
					mesh[ i++ ] = new Number3D( (y+1)*tileSize.y, heightMap[ (y+1)*w+x+1 ], (x+1)*tileSize.x );
				}
			}
			
			return new Mesh3D( null, frames, uv );
		}
		
		private function prepareTexture( textureCount:int, textureSize:int ):BitmapData
		{
			this.textureGridSize = 1;
			while( this.textureGridSize*this.textureGridSize < textureCount )
			{
				++this.textureGridSize;
			}
			
			this.textureTileSize = textureSize;
			return new BitmapData( 
				textureSize*this.textureGridSize, 
				textureSize*this.textureGridSize,
				false,
				0x00A050
				);
		}
	}
}